
package ch.hearc.meteo.imp.com.real.com;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

import ch.hearc.meteo.imp.com.real.com.trame.TrameDecoder;
import ch.hearc.meteo.spec.meteo.MeteoPortDetectionService_I;

public class MeteoPortDetectionService implements MeteoPortDetectionService_I
	{

	/*------------------------------------------------------------------*\
	|*							Constructeurs							*|
	\*------------------------------------------------------------------*/

	/*------------------------------------------------------------------*\
	|*							Methodes Public							*|
	\*------------------------------------------------------------------*/

	@Override
	public List<String> findPortNameMeteo()
		{
		List<String> listeMeteoStation = new ArrayList<String>();

		for(String portName:findPortSerie())
			{
			if (isStationMeteoAvailable(portName))
				{
				listeMeteoStation.add(portName);
				System.out.println("ok : " + portName);
				}
			else
				{
				System.out.println("fail : " + portName);
				}
			}

		return listeMeteoStation;
		}

	@Override
	public List<String> findPortNameMeteo(List<String> listPortExcluded)
		{
		List<String> listeMeteoStation = findPortNameMeteo();
		listeMeteoStation.removeAll(listPortExcluded);
		return listeMeteoStation;
		}

	@Override
	public List<String> findPortSerie()
		{
		List<String> portsList = new ArrayList<String>();
		Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
		while(portEnum.hasMoreElements())
			{
			CommPortIdentifier identifier = portEnum.nextElement();
			if (identifier.getPortType() == CommPortIdentifier.PORT_SERIAL && !identifier.isCurrentlyOwned())
				{
				portsList.add(identifier.getName());
				}
			}
		return portsList;
		}

	@Override
	public boolean isStationMeteoAvailable(String portName)
		{
		try
			{
			if (openPort(portName))
				{

				System.out.println("ask " + portName);
				askAltitudeAsync();
				//askAltitudeWithTimeout(WRITE_TIMEOUT);
				byte[] trame = readDataWithTimeout(READ_TIMEOUT);//lireDonnees();
				System.out.println("trame : " + portName + " " + Arrays.toString(trame));
				TrameDecoder.dataType(trame);

				closePort(CLOSE_TIMEOUT);
				}
			else
				{
				return false;
				}
			}
		catch (Exception e)
			{
			closePort(CLOSE_TIMEOUT);
			return false;
			}
		return true;
		}

	private void askAltitudeAsync() throws Exception
		{
		byte[] requete = { CHAR_DEBUT_TRAME, CHAR_REQUETE_ALTITUDE, CHAR_FIN_TRAME };
		ecrireDonnees(requete);
		}

	private boolean askAltitudeWithTimeout(long timeout)
		{
		ExecutorService executor = null;
		try
			{
			Callable<Void> writeTask = new Callable<Void>()
				{

					@Override
					public Void call() throws Exception
						{
						askAltitudeAsync();
						return null;
						}
				};
			executor = Executors.newFixedThreadPool(1);
			executor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
			Future<Void> future = executor.submit(writeTask);
			future.get(timeout, TimeUnit.MILLISECONDS);
			executor.shutdown();
			}
		catch (Exception e)
			{
			System.out.println("échec ask");
			executor.shutdown();
			return false;
			}
		return true;
		}

	private byte[] readDataWithTimeout(long timeout)
		{
		ExecutorService executor = null;
		try
			{
			Callable<byte[]> readTask = new Callable<byte[]>()
				{

					@Override
					public byte[] call() throws Exception
						{
						return lireDonnees();
						}
				};
			executor = Executors.newFixedThreadPool(1);
			executor.awaitTermination(timeout + 50, TimeUnit.MILLISECONDS);
			Future<byte[]> future = executor.submit(readTask);
			byte[] result = future.get(timeout, TimeUnit.MILLISECONDS);
			executor.shutdown();
			return result;
			}
		catch (Exception e)
			{
			executor.shutdown();
			return null;
			}
		}

	/*------------------------------------------------------------------*\
	|*							Methodes Private						*|
	\*------------------------------------------------------------------*/

	private synchronized boolean openPort(String portName)
		{
		try
			{
			ComOption comOption = new ComOption();
			CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(portName);
			int openDelay = 2000;
			serialPort = (SerialPort)portId.open(ComConnexion.class.getSimpleName(), openDelay);
			comOption.applyTo(serialPort);
			serialPort.notifyOnDataAvailable(false);

			is = serialPort.getInputStream();
			os = serialPort.getOutputStream();
			}
		catch (Exception e)
			{
			return false;
			}
		return true;
		}

	private synchronized void closePort(long timeout)
		{
		ExecutorService executor = null;
		try
			{
			Callable<Void> readTask = new Callable<Void>()
				{

					@Override
					public Void call() throws Exception
						{
						try
							{
							serialPort.removeEventListener();
							is.close();
							os.close();
							serialPort.close();
							}
						catch (Exception e)
							{
							e.printStackTrace();
							}
						return null;
						}
				};
			executor = Executors.newFixedThreadPool(1);
			executor.awaitTermination(timeout + 50, TimeUnit.MILLISECONDS);
			Future<Void> future = executor.submit(readTask);
			Void result = future.get(timeout, TimeUnit.MILLISECONDS);
			executor.shutdown();
			}
		catch (Exception e)
			{
			executor.shutdown();
			}
		}

	private synchronized byte[] lireDonnees()
		{

		byte[] buffer = new byte[1024];
		byte[] result = new byte[10];

		int i = 0;
		byte c = 0;
		byte lastC = 0;

		try
			{
			System.out.println("reading..");
			while((c = (byte)is.read()) != -1 && i < 1024)
				{
				buffer[i] = c;
				if (lastC == '\r' && c == '\n')
					{
					for(int j = 0; j < 10; j++)
						{
						result[9 - j] = buffer[i - j];
						}
					return result;
					}
				lastC = c;
				i++;
				}
			}
		catch (IOException e)
			{
			e.printStackTrace();
			}

		return null;
		}

	private void ecrireDonnees(byte[] data)
		{
		try
			{
			os.write(data);
			//os.flush();
			}
		catch (IOException e)
			{
			e.printStackTrace();
			}
		}

	/*------------------------------------------------------------------*\
	|*							Attributs Private						*|
	\*------------------------------------------------------------------*/

	//Tools
	private SerialPort serialPort;
	private BufferedWriter writer;
	private BufferedReader reader;
	private InputStream is;
	private OutputStream os;

	private static byte CHAR_DEBUT_TRAME = 0x01;
	private static byte CHAR_FIN_TRAME = 0x00;
	private static byte CHAR_REQUETE_ALTITUDE = 0x02;

	private static final long READ_TIMEOUT = 100;
	private static final long CLOSE_TIMEOUT = 250;

	}
